/*
  This file is part of JCM.

  JCM is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  JCM is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with JCM.  If not, see <http://www.gnu.org/licenses/>.
*/
import java.awt.event.*;
import java.awt.*;
import java.text.*;
import javax.swing.*;
import javax.swing.text.*;

/**
 * Implementação de editor de células usando campo de texto formatado
 * para editar valores tipo Double com entrada de valores limitados a
 * um intervalo fornecido.
 * A formatação usa Locale pt-BR com visualização mínima de 4 e máxima de
 * 6 dígitos na parte fracionária, porém internamente preserva o valor
 * digitado que for além do visualizado.
*/
public class DoubleEditor extends DefaultCellEditor
{
  /**
   * Construtor da classe especializada na entrada de valores tipo Double.
   *
   * @param min Valor mínimo permitido.
   * @param max Valor máximo permitido.
  */
  public DoubleEditor(double min, double max)
  {
    // invoca a superclasse usando um JFormattedTextField extendido
    super(new JTextFieldDoubleEditor());

    // instancia um DecimalFormat usando Locale BR e pattern apropriado
    // a visualização de 4 a 6 dígitos na parte fracionária
    NumberFormat format = NumberFormatFactory.getDecimalFormat("0.0000##");

    // instancia um formatador de números com formato especifico
    // e restrição de domínio
    NumberFormatter nf = new NumberFormatter(format);
    /* nf.setFormat(format); */
    nf.setMinimum(new Double(min));
    nf.setMaximum(new Double(max));

    // ajusta o comportamento do campo de texto associado
    JFormattedTextField f = (JFormattedTextField) getComponent();
    f.setFormatterFactory(new DefaultFormatterFactory(nf));
    f.setFocusLostBehavior(JFormattedTextField.PERSIST);
    f.setHorizontalAlignment(JTextField.RIGHT);
    f.getInputMap().put(KeyStroke.getKeyStroke("Enter"), "COMMIT");
    f.getActionMap().put("COMMIT",
      new AbstractAction() {
        public void actionPerformed(ActionEvent e)
        {
          JFormattedTextField ftf = (JFormattedTextField) e.getSource();
          if (! ftf.isEditValid()) {
            if (userSaysRevert()) ftf.postActionEvent();
          } else {
            try {
              ftf.commitEdit();
              ftf.postActionEvent();
            } catch (java.text.ParseException pe) {
            }
          }
        }
        private static final long serialVersionUID = 2689379913287424332L;
    });

    errorMsg = String.format("<html>Espera-se um valor entre <b>%6.2f</b> e <b>%6.2f</b>.<br>Continue editando ou reverta o valor anterior.</html>", min, max);
  }

  /**
   * Invoca o método de assinalação de valor no campo de texto formatado.
   *
   * @param table Componente pai da célula que usa esse editor.
   * @param value Objeto a assinalar para a célula.
   * @param isSelected Status de seleção da célula.
   * @param row Número da linha da célula.
   * @param column Número da coluna da célula.
  */
  @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
  {
    final JFormattedTextField f = (JFormattedTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
    f.setValue(value);
    // bug (#4192625) fix
    f.selectAll();
    f.getCaret().setVisible(true);
    EventQueue.invokeLater(new Runnable(){
      public void run() { f.requestFocus(); }
    });
    return f;
  }

  /**
   * Acessa o objeto associado à célula que usa esse editor.
   *
   * @return Objeto associado à célula que usa esse editor se for do tipo
   *         Double e em caso contrário retorna null.
  */
  @Override public Object getCellEditorValue()
  {
    Object value = ((JFormattedTextField) getComponent()).getValue();
    if (value instanceof Double) {
      return value;
    } else if (value instanceof Number) {
      return new Double(((Number) value).doubleValue());
    } else {
      return null;
    }
  }

  /**
   * Encerra a edição e atualiza o valor associado ao campo se o texto
   * editado é válido e em caso contrário notifica o usuário sobre o fato
   * oferecendo as opções de continuar a edição ou retornar o valor prévio.
   * Nota: Quando o texto editado é válido, sua super-classe é invocada
   *       para notificar os listeners interessados no evento.
   *
   * @return Status do encerramento de edição do campo.
  */
  @Override public boolean stopCellEditing()
  {
    JFormattedTextField f = (JFormattedTextField) getComponent();
    if (f.isEditValid()) {
      try {
        f.commitEdit();
      } catch (java.text.ParseException e) {
      }
    } else if (!userSaysRevert()) {
      return false;
    }
    return super.stopCellEditing();
  }

  /**
   * Avisa o usuário que o texto digitado é inválido.
   *
   * @return <b>true</b> se o usuário opta por reverter ao valor válido
   *         prévio ou <b>false</b> se opta por continuar a edição.
  */
  protected boolean userSaysRevert()
  {
    Toolkit.getDefaultToolkit().beep();
    JFormattedTextField f = (JFormattedTextField) getComponent();
    f.selectAll();
    Object[] options = {"Editar", "Reverter"};
    int answer = JOptionPane.showOptionDialog(
      SwingUtilities.getWindowAncestor(f), errorMsg, "Texto Inválido",
      JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, options,
      options[1]);
    if (answer != 1) return false;
    // reverte ao valor anterior
    f.setValue(f.getValue());
    return true;
  }

  private String errorMsg;

  private static final long serialVersionUID = 1054331620322894030L;
}
